// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/ic/binary-op-assembler.h"

#include "src/globals.h"

#include "src/objects-inl.h" // weolar
#include "src/objects/dictionary-inl.h" // weolar

namespace v8 {
namespace internal {

    using compiler::Node;

    Node* BinaryOpAssembler::Generate_AddWithFeedback(Node* context, Node* lhs,
        Node* rhs, Node* slot_id,
        Node* feedback_vector,
        bool rhs_is_smi)
    {
        // Shared entry for floating point addition.
        Label do_fadd(this), if_lhsisnotnumber(this, Label::kDeferred),
            check_rhsisoddball(this, Label::kDeferred),
            call_with_oddball_feedback(this), call_with_any_feedback(this),
            call_add_stub(this), end(this), bigint(this, Label::kDeferred);
        VARIABLE(var_fadd_lhs, MachineRepresentation::kFloat64);
        VARIABLE(var_fadd_rhs, MachineRepresentation::kFloat64);
        VARIABLE(var_type_feedback, MachineRepresentation::kTaggedSigned);
        VARIABLE(var_result, MachineRepresentation::kTagged);

        // Check if the {lhs} is a Smi or a HeapObject.
        Label if_lhsissmi(this);
        // If rhs is known to be an Smi we want to fast path Smi operation. This is
        // for AddSmi operation. For the normal Add operation, we want to fast path
        // both Smi and Number operations, so this path should not be marked as
        // Deferred.
        Label if_lhsisnotsmi(this,
            rhs_is_smi ? Label::kDeferred : Label::kNonDeferred);
        Branch(TaggedIsNotSmi(lhs), &if_lhsisnotsmi, &if_lhsissmi);

        BIND(&if_lhsissmi);
        {
            Comment("lhs is Smi");
            if (!rhs_is_smi) {
                // Check if the {rhs} is also a Smi.
                Label if_rhsissmi(this), if_rhsisnotsmi(this);
                Branch(TaggedIsSmi(rhs), &if_rhsissmi, &if_rhsisnotsmi);

                BIND(&if_rhsisnotsmi);
                {
                    // Check if the {rhs} is a HeapNumber.
                    GotoIfNot(IsHeapNumber(rhs), &check_rhsisoddball);

                    var_fadd_lhs.Bind(SmiToFloat64(lhs));
                    var_fadd_rhs.Bind(LoadHeapNumberValue(rhs));
                    Goto(&do_fadd);
                }

                BIND(&if_rhsissmi);
            }

            {
                Comment("perform smi operation");
                // If rhs is known to be an Smi we want to fast path Smi operation. This
                // is for AddSmi operation. For the normal Add operation, we want to fast
                // path both Smi and Number operations, so this path should not be marked
                // as Deferred.
                Label if_overflow(this,
                    rhs_is_smi ? Label::kDeferred : Label::kNonDeferred);
                TNode<Smi> smi_result = TrySmiAdd(CAST(lhs), CAST(rhs), &if_overflow);
                // Not overflowed.
                {
                    var_type_feedback.Bind(
                        SmiConstant(BinaryOperationFeedback::kSignedSmall));
                    var_result.Bind(smi_result);
                    Goto(&end);
                }

                BIND(&if_overflow);
                {
                    var_fadd_lhs.Bind(SmiToFloat64(lhs));
                    var_fadd_rhs.Bind(SmiToFloat64(rhs));
                    Goto(&do_fadd);
                }
            }
        }

        BIND(&if_lhsisnotsmi);
        {
            // Check if {lhs} is a HeapNumber.
            GotoIfNot(IsHeapNumber(lhs), &if_lhsisnotnumber);

            if (!rhs_is_smi) {
                // Check if the {rhs} is Smi.
                Label if_rhsissmi(this), if_rhsisnotsmi(this);
                Branch(TaggedIsSmi(rhs), &if_rhsissmi, &if_rhsisnotsmi);

                BIND(&if_rhsisnotsmi);
                {
                    // Check if the {rhs} is a HeapNumber.
                    GotoIfNot(IsHeapNumber(rhs), &check_rhsisoddball);

                    var_fadd_lhs.Bind(LoadHeapNumberValue(lhs));
                    var_fadd_rhs.Bind(LoadHeapNumberValue(rhs));
                    Goto(&do_fadd);
                }

                BIND(&if_rhsissmi);
            }
            {
                var_fadd_lhs.Bind(LoadHeapNumberValue(lhs));
                var_fadd_rhs.Bind(SmiToFloat64(rhs));
                Goto(&do_fadd);
            }
        }

        BIND(&do_fadd);
        {
            var_type_feedback.Bind(SmiConstant(BinaryOperationFeedback::kNumber));
            Node* value = Float64Add(var_fadd_lhs.value(), var_fadd_rhs.value());
            Node* result = AllocateHeapNumberWithValue(value);
            var_result.Bind(result);
            Goto(&end);
        }

        BIND(&if_lhsisnotnumber);
        {
            // No checks on rhs are done yet. We just know lhs is not a number or Smi.
            Label if_lhsisoddball(this), if_lhsisnotoddball(this);
            Node* lhs_instance_type = LoadInstanceType(lhs);
            Node* lhs_is_oddball = InstanceTypeEqual(lhs_instance_type, ODDBALL_TYPE);
            Branch(lhs_is_oddball, &if_lhsisoddball, &if_lhsisnotoddball);

            BIND(&if_lhsisoddball);
            {
                GotoIf(TaggedIsSmi(rhs), &call_with_oddball_feedback);

                // Check if {rhs} is a HeapNumber.
                Branch(IsHeapNumber(rhs), &call_with_oddball_feedback,
                    &check_rhsisoddball);
            }

            BIND(&if_lhsisnotoddball);
            {
                Label lhs_is_string(this), lhs_is_bigint(this);
                GotoIf(IsStringInstanceType(lhs_instance_type), &lhs_is_string);
                GotoIf(IsBigIntInstanceType(lhs_instance_type), &lhs_is_bigint);
                Goto(&call_with_any_feedback);

                BIND(&lhs_is_bigint);
                {
                    GotoIf(TaggedIsSmi(rhs), &call_with_any_feedback);
                    Branch(IsBigInt(rhs), &bigint, &call_with_any_feedback);
                }

                BIND(&lhs_is_string);
                // Check if the {rhs} is a smi, and exit the string check early if it is.
                GotoIf(TaggedIsSmi(rhs), &call_with_any_feedback);

                Node* rhs_instance_type = LoadInstanceType(rhs);

                // Exit unless {rhs} is a string. Since {lhs} is a string we no longer
                // need an Oddball check.
                GotoIfNot(IsStringInstanceType(rhs_instance_type),
                    &call_with_any_feedback);

                var_type_feedback.Bind(SmiConstant(BinaryOperationFeedback::kString));
                var_result.Bind(
                    CallBuiltin(Builtins::kStringAdd_CheckNone, context, lhs, rhs));

                Goto(&end);
            }
        }

        BIND(&check_rhsisoddball);
        {
            // Check if rhs is an oddball. At this point we know lhs is either a
            // Smi or number or oddball and rhs is not a number or Smi.
            Node* rhs_instance_type = LoadInstanceType(rhs);
            Node* rhs_is_oddball = InstanceTypeEqual(rhs_instance_type, ODDBALL_TYPE);
            GotoIf(rhs_is_oddball, &call_with_oddball_feedback);
            Branch(IsBigIntInstanceType(rhs_instance_type), &bigint,
                &call_with_any_feedback);
        }

        BIND(&bigint);
        {
            var_type_feedback.Bind(SmiConstant(BinaryOperationFeedback::kBigInt));
            var_result.Bind(CallRuntime(Runtime::kBigIntBinaryOp, context, lhs, rhs,
                SmiConstant(Operation::kAdd)));
            Goto(&end);
        }

        BIND(&call_with_oddball_feedback);
        {
            var_type_feedback.Bind(
                SmiConstant(BinaryOperationFeedback::kNumberOrOddball));
            Goto(&call_add_stub);
        }

        BIND(&call_with_any_feedback);
        {
            var_type_feedback.Bind(SmiConstant(BinaryOperationFeedback::kAny));
            Goto(&call_add_stub);
        }

        BIND(&call_add_stub);
        {
            var_result.Bind(CallBuiltin(Builtins::kAdd, context, lhs, rhs));
            Goto(&end);
        }

        BIND(&end);
        UpdateFeedback(var_type_feedback.value(), feedback_vector, slot_id);
        return var_result.value();
    }

    Node* BinaryOpAssembler::Generate_BinaryOperationWithFeedback(
        Node* context, Node* lhs, Node* rhs, Node* slot_id, Node* feedback_vector,
        const SmiOperation& smiOperation, const FloatOperation& floatOperation,
        Operation op, bool rhs_is_smi)
    {
        Label do_float_operation(this), end(this), call_stub(this),
            check_rhsisoddball(this, Label::kDeferred), call_with_any_feedback(this),
            if_lhsisnotnumber(this, Label::kDeferred),
            if_bigint(this, Label::kDeferred);
        VARIABLE(var_float_lhs, MachineRepresentation::kFloat64);
        VARIABLE(var_float_rhs, MachineRepresentation::kFloat64);
        VARIABLE(var_type_feedback, MachineRepresentation::kTaggedSigned);
        VARIABLE(var_result, MachineRepresentation::kTagged);

        Label if_lhsissmi(this);
        // If rhs is known to be an Smi (in the SubSmi, MulSmi, DivSmi, ModSmi
        // bytecode handlers) we want to fast path Smi operation. For the normal
        // operation, we want to fast path both Smi and Number operations, so this
        // path should not be marked as Deferred.
        Label if_lhsisnotsmi(this,
            rhs_is_smi ? Label::kDeferred : Label::kNonDeferred);
        Branch(TaggedIsNotSmi(lhs), &if_lhsisnotsmi, &if_lhsissmi);

        // Check if the {lhs} is a Smi or a HeapObject.
        BIND(&if_lhsissmi);
        {
            Comment("lhs is Smi");
            if (!rhs_is_smi) {
                // Check if the {rhs} is also a Smi.
                Label if_rhsissmi(this), if_rhsisnotsmi(this);
                Branch(TaggedIsSmi(rhs), &if_rhsissmi, &if_rhsisnotsmi);
                BIND(&if_rhsisnotsmi);
                {
                    // Check if {rhs} is a HeapNumber.
                    GotoIfNot(IsHeapNumber(rhs), &check_rhsisoddball);

                    // Perform a floating point operation.
                    var_float_lhs.Bind(SmiToFloat64(lhs));
                    var_float_rhs.Bind(LoadHeapNumberValue(rhs));
                    Goto(&do_float_operation);
                }

                BIND(&if_rhsissmi);
            }

            {
                Comment("perform smi operation");
                var_result.Bind(smiOperation(lhs, rhs, &var_type_feedback));
                Goto(&end);
            }
        }

        BIND(&if_lhsisnotsmi);
        {
            Comment("lhs is not Smi");
            // Check if the {lhs} is a HeapNumber.
            GotoIfNot(IsHeapNumber(lhs), &if_lhsisnotnumber);

            if (!rhs_is_smi) {
                // Check if the {rhs} is a Smi.
                Label if_rhsissmi(this), if_rhsisnotsmi(this);
                Branch(TaggedIsSmi(rhs), &if_rhsissmi, &if_rhsisnotsmi);

                BIND(&if_rhsisnotsmi);
                {
                    // Check if the {rhs} is a HeapNumber.
                    GotoIfNot(IsHeapNumber(rhs), &check_rhsisoddball);

                    // Perform a floating point operation.
                    var_float_lhs.Bind(LoadHeapNumberValue(lhs));
                    var_float_rhs.Bind(LoadHeapNumberValue(rhs));
                    Goto(&do_float_operation);
                }

                BIND(&if_rhsissmi);
            }

            {
                // Perform floating point operation.
                var_float_lhs.Bind(LoadHeapNumberValue(lhs));
                var_float_rhs.Bind(SmiToFloat64(rhs));
                Goto(&do_float_operation);
            }
        }

        BIND(&do_float_operation);
        {
            var_type_feedback.Bind(SmiConstant(BinaryOperationFeedback::kNumber));
            Node* lhs_value = var_float_lhs.value();
            Node* rhs_value = var_float_rhs.value();
            Node* value = floatOperation(lhs_value, rhs_value);
            var_result.Bind(AllocateHeapNumberWithValue(value));
            Goto(&end);
        }

        BIND(&if_lhsisnotnumber);
        {
            // No checks on rhs are done yet. We just know lhs is not a number or Smi.
            Label if_left_bigint(this), if_left_oddball(this);
            Node* lhs_instance_type = LoadInstanceType(lhs);
            GotoIf(IsBigIntInstanceType(lhs_instance_type), &if_left_bigint);
            Node* lhs_is_oddball = InstanceTypeEqual(lhs_instance_type, ODDBALL_TYPE);
            Branch(lhs_is_oddball, &if_left_oddball, &call_with_any_feedback);

            BIND(&if_left_oddball);
            {
                Label if_rhsissmi(this), if_rhsisnotsmi(this);
                Branch(TaggedIsSmi(rhs), &if_rhsissmi, &if_rhsisnotsmi);

                BIND(&if_rhsissmi);
                {
                    var_type_feedback.Bind(
                        SmiConstant(BinaryOperationFeedback::kNumberOrOddball));
                    Goto(&call_stub);
                }

                BIND(&if_rhsisnotsmi);
                {
                    // Check if {rhs} is a HeapNumber.
                    GotoIfNot(IsHeapNumber(rhs), &check_rhsisoddball);

                    var_type_feedback.Bind(
                        SmiConstant(BinaryOperationFeedback::kNumberOrOddball));
                    Goto(&call_stub);
                }
            }

            BIND(&if_left_bigint);
            {
                GotoIf(TaggedIsSmi(rhs), &call_with_any_feedback);
                Branch(IsBigInt(rhs), &if_bigint, &call_with_any_feedback);
            }
        }

        BIND(&check_rhsisoddball);
        {
            // Check if rhs is an oddball. At this point we know lhs is either a
            // Smi or number or oddball and rhs is not a number or Smi.
            Node* rhs_instance_type = LoadInstanceType(rhs);
            GotoIf(IsBigIntInstanceType(rhs_instance_type), &if_bigint);
            Node* rhs_is_oddball = InstanceTypeEqual(rhs_instance_type, ODDBALL_TYPE);
            GotoIfNot(rhs_is_oddball, &call_with_any_feedback);

            var_type_feedback.Bind(
                SmiConstant(BinaryOperationFeedback::kNumberOrOddball));
            Goto(&call_stub);
        }

        // This handles the case where at least one input is a BigInt.
        BIND(&if_bigint);
        {
            var_type_feedback.Bind(SmiConstant(BinaryOperationFeedback::kBigInt));
            var_result.Bind(CallRuntime(Runtime::kBigIntBinaryOp, context, lhs, rhs,
                SmiConstant(op)));
            Goto(&end);
        }

        BIND(&call_with_any_feedback);
        {
            var_type_feedback.Bind(SmiConstant(BinaryOperationFeedback::kAny));
            Goto(&call_stub);
        }

        BIND(&call_stub);
        {
            Node* result;
            switch (op) {
            case Operation::kSubtract:
                result = CallBuiltin(Builtins::kSubtract, context, lhs, rhs);
                break;
            case Operation::kMultiply:
                result = CallBuiltin(Builtins::kMultiply, context, lhs, rhs);
                break;
            case Operation::kDivide:
                result = CallBuiltin(Builtins::kDivide, context, lhs, rhs);
                break;
            case Operation::kModulus:
                result = CallBuiltin(Builtins::kModulus, context, lhs, rhs);
                break;
            default:
                UNREACHABLE();
            }
            var_result.Bind(result);
            Goto(&end);
        }

        BIND(&end);
        UpdateFeedback(var_type_feedback.value(), feedback_vector, slot_id);
        return var_result.value();
    }

    Node* BinaryOpAssembler::Generate_SubtractWithFeedback(Node* context, Node* lhs,
        Node* rhs, Node* slot_id,
        Node* feedback_vector,
        bool rhs_is_smi)
    {
        auto smiFunction = [=](Node* lhs, Node* rhs, Variable* var_type_feedback) {
            Label end(this);
            TVARIABLE(Number, var_result);
            // If rhs is known to be an Smi (for SubSmi) we want to fast path Smi
            // operation. For the normal Sub operation, we want to fast path both
            // Smi and Number operations, so this path should not be marked as Deferred.
            Label if_overflow(this,
                rhs_is_smi ? Label::kDeferred : Label::kNonDeferred);
            var_result = TrySmiSub(CAST(lhs), CAST(rhs), &if_overflow);
            var_type_feedback->Bind(SmiConstant(BinaryOperationFeedback::kSignedSmall));
            Goto(&end);

            BIND(&if_overflow);
            {
                var_type_feedback->Bind(SmiConstant(BinaryOperationFeedback::kNumber));
                Node* value = Float64Sub(SmiToFloat64(lhs), SmiToFloat64(rhs));
                var_result = AllocateHeapNumberWithValue(value);
                Goto(&end);
            }

            BIND(&end);
            return var_result.value();
        };
        auto floatFunction = [=](Node* lhs, Node* rhs) {
            return Float64Sub(lhs, rhs);
        };
        return Generate_BinaryOperationWithFeedback(
            context, lhs, rhs, slot_id, feedback_vector, smiFunction, floatFunction,
            Operation::kSubtract, rhs_is_smi);
    }

    Node* BinaryOpAssembler::Generate_MultiplyWithFeedback(Node* context, Node* lhs,
        Node* rhs, Node* slot_id,
        Node* feedback_vector,
        bool rhs_is_smi)
    {
        auto smiFunction = [=](Node* lhs, Node* rhs, Variable* var_type_feedback) {
            TNode<Number> result = SmiMul(CAST(lhs), CAST(rhs));
            var_type_feedback->Bind(SelectSmiConstant(
                TaggedIsSmi(result), BinaryOperationFeedback::kSignedSmall,
                BinaryOperationFeedback::kNumber));
            return result;
        };
        auto floatFunction = [=](Node* lhs, Node* rhs) {
            return Float64Mul(lhs, rhs);
        };
        return Generate_BinaryOperationWithFeedback(
            context, lhs, rhs, slot_id, feedback_vector, smiFunction, floatFunction,
            Operation::kMultiply, rhs_is_smi);
    }

    Node* BinaryOpAssembler::Generate_DivideWithFeedback(
        Node* context, Node* dividend, Node* divisor, Node* slot_id,
        Node* feedback_vector, bool rhs_is_smi)
    {
        auto smiFunction = [=](Node* lhs, Node* rhs, Variable* var_type_feedback) {
            VARIABLE(var_result, MachineRepresentation::kTagged);
            // If rhs is known to be an Smi (for DivSmi) we want to fast path Smi
            // operation. For the normal Div operation, we want to fast path both
            // Smi and Number operations, so this path should not be marked as Deferred.
            Label bailout(this, rhs_is_smi ? Label::kDeferred : Label::kNonDeferred),
                end(this);
            var_result.Bind(TrySmiDiv(CAST(lhs), CAST(rhs), &bailout));
            var_type_feedback->Bind(SmiConstant(BinaryOperationFeedback::kSignedSmall));
            Goto(&end);

            BIND(&bailout);
            {
                var_type_feedback->Bind(
                    SmiConstant(BinaryOperationFeedback::kSignedSmallInputs));
                Node* value = Float64Div(SmiToFloat64(lhs), SmiToFloat64(rhs));
                var_result.Bind(AllocateHeapNumberWithValue(value));
                Goto(&end);
            }

            BIND(&end);
            return var_result.value();
        };
        auto floatFunction = [=](Node* lhs, Node* rhs) {
            return Float64Div(lhs, rhs);
        };
        return Generate_BinaryOperationWithFeedback(
            context, dividend, divisor, slot_id, feedback_vector, smiFunction,
            floatFunction, Operation::kDivide, rhs_is_smi);
    }

    Node* BinaryOpAssembler::Generate_ModulusWithFeedback(
        Node* context, Node* dividend, Node* divisor, Node* slot_id,
        Node* feedback_vector, bool rhs_is_smi)
    {
        auto smiFunction = [=](Node* lhs, Node* rhs, Variable* var_type_feedback) {
            TNode<Number> result = SmiMod(CAST(lhs), CAST(rhs));
            var_type_feedback->Bind(SelectSmiConstant(
                TaggedIsSmi(result), BinaryOperationFeedback::kSignedSmall,
                BinaryOperationFeedback::kNumber));
            return result;
        };
        auto floatFunction = [=](Node* lhs, Node* rhs) {
            return Float64Mod(lhs, rhs);
        };
        return Generate_BinaryOperationWithFeedback(
            context, dividend, divisor, slot_id, feedback_vector, smiFunction,
            floatFunction, Operation::kModulus, rhs_is_smi);
    }

    Node* BinaryOpAssembler::Generate_ExponentiateWithFeedback(
        Node* context, Node* base, Node* exponent, Node* slot_id,
        Node* feedback_vector, bool rhs_is_smi)
    {
        // We currently don't optimize exponentiation based on feedback.
        Node* dummy_feedback = SmiConstant(BinaryOperationFeedback::kAny);
        UpdateFeedback(dummy_feedback, feedback_vector, slot_id);
        return CallBuiltin(Builtins::kExponentiate, context, base, exponent);
    }

} // namespace internal
} // namespace v8
